/*
 * Copyright (C) 2018-2025 Toshiaki Maki <makingx@gmail.com>
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *         http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
package am.ik.yavi.arguments;

import java.util.Locale;
import java.util.Objects;
import java.util.function.Function;
import java.util.function.Supplier;

import am.ik.yavi.core.ConstraintContext;
import am.ik.yavi.core.ConstraintGroup;
import am.ik.yavi.core.ConstraintViolationsException;
import am.ik.yavi.core.Validated;
import am.ik.yavi.core.ValueValidator;
import am.ik.yavi.jsr305.Nullable;

/**
 * Generated by https://github.com/making/yavi/blob/develop/scripts/generate-args.sh
 *
 * @since 0.3.0
 */
@FunctionalInterface
public interface Arguments4Validator<A1, A2, A3, A4, X> {

	/**
	 * Convert an Arguments1Validator that validates Arguments4 to an Arguments4Validator
	 * @param validator validator for Arguments4
	 * @param <A1> type of first argument
	 * @param <A2> type of argument at position 2
	 * @param <A3> type of argument at position 3
	 * @param <A4> type of argument at position 4
	 * @param <X> target result type
	 * @return arguments4 validator that takes arguments directly
	 * @since 0.16.0
	 */
	static <A1, A2, A3, A4, X> Arguments4Validator<A1, A2, A3, A4, X> unwrap(
			Arguments1Validator<Arguments4<A1, A2, A3, A4>, X> validator) {
		return new Arguments4Validator<A1, A2, A3, A4, X>() {
			@Override
			public Validated<X> validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4,
					Locale locale, ConstraintContext constraintContext) {
				return validator.validate(Arguments.of(a1, a2, a3, a4), locale, constraintContext);
			}

			@Override
			public Arguments4Validator<A1, A2, A3, A4, Supplier<X>> lazy() {
				return Arguments4Validator.unwrap(validator.lazy());
			}
		};
	}

	Validated<X> validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4, Locale locale,
			ConstraintContext constraintContext);

	/**
	 * Convert this validator to one that validates Arguments4 as a single object.
	 * @return a validator that takes an Arguments4
	 * @since 0.16.0
	 */
	default Arguments1Validator<Arguments4<A1, A2, A3, A4>, X> wrap() {
		return new Arguments1Validator<Arguments4<A1, A2, A3, A4>, X>() {
			@Override
			public Validated<X> validate(Arguments4<A1, A2, A3, A4> args, Locale locale,
					ConstraintContext constraintContext) {
				final Arguments4<? extends A1, ? extends A2, ? extends A3, ? extends A4> nonNullArgs = Objects
					.requireNonNull(args);
				return Arguments4Validator.this.validate(nonNullArgs.arg1(), nonNullArgs.arg2(), nonNullArgs.arg3(),
						nonNullArgs.arg4(), locale, constraintContext);
			}

			@Override
			public Arguments1Validator<Arguments4<A1, A2, A3, A4>, Supplier<X>> lazy() {
				return Arguments4Validator.this.lazy().wrap();
			}
		};
	}

	/**
	 * @deprecated Use {@link #map(Function)} instead.
	 * @since 0.7.0
	 */
	@Deprecated
	default <X2> Arguments4Validator<A1, A2, A3, A4, X2> andThen(Function<? super X, ? extends X2> mapper) {
		return this.map(mapper);
	}

	/**
	 * Maps the validated value to a new type using the provided mapper function. This is
	 * a transformation operation that applies the function only if validation succeeds.
	 * @param mapper function to transform the validated value
	 * @param <X2> the type after transformation
	 * @return a value validator that applies the mapping function after validation
	 * @since 0.17.0
	 */
	default <X2> Arguments4Validator<A1, A2, A3, A4, X2> map(Function<? super X, ? extends X2> mapper) {
		return new Arguments4Validator<A1, A2, A3, A4, X2>() {
			@Override
			public Validated<X2> validate(A1 a1, A2 a2, A3 a3, A4 a4, Locale locale,
					ConstraintContext constraintContext) {
				return Arguments4Validator.this.validate(a1, a2, a3, a4, locale, constraintContext).map(mapper);
			}

			@Override
			public Arguments4Validator<A1, A2, A3, A4, Supplier<X2>> lazy() {
				return Arguments4Validator.this.lazy()
					.map((Function<Supplier<X>, Supplier<X2>>) xSupplier -> () -> mapper.apply(xSupplier.get()));
			}
		};
	}

	/**
	 * @since 0.11.0
	 */
	default <X2> Arguments4Validator<A1, A2, A3, A4, X2> andThen(ValueValidator<? super X, X2> validator) {
		return new Arguments4Validator<A1, A2, A3, A4, X2>() {
			@Override
			public Validated<X2> validate(A1 a1, A2 a2, A3 a3, A4 a4, Locale locale,
					ConstraintContext constraintContext) {
				return Arguments4Validator.this.validate(a1, a2, a3, a4, locale, constraintContext)
					.flatMap(v -> validator.validate(v, locale, constraintContext));
			}

			@Override
			public Arguments4Validator<A1, A2, A3, A4, Supplier<X2>> lazy() {
				return Arguments4Validator.this.lazy()
					.andThen((xSupplier, locale, constraintContext) -> validator
						.validate(Objects.requireNonNull(xSupplier).get(), locale, constraintContext)
						.map(x2 -> () -> x2));
			}
		};
	}

	/**
	 * @since 0.7.0
	 */
	default <A> Arguments1Validator<A, X> compose(
			Function<? super A, ? extends Arguments4<? extends A1, ? extends A2, ? extends A3, ? extends A4>> mapper) {
		return new Arguments1Validator<A, X>() {
			@Override
			public Validated<X> validate(A a, Locale locale, ConstraintContext constraintContext) {
				final Arguments4<? extends A1, ? extends A2, ? extends A3, ? extends A4> args = mapper.apply(a);
				return Arguments4Validator.this.validate(args.arg1(), args.arg2(), args.arg3(), args.arg4(), locale,
						constraintContext);
			}

			@Override
			public Arguments1Validator<A, Supplier<X>> lazy() {
				return Arguments4Validator.this.lazy().compose(mapper);
			}
		};
	}

	/**
	 * @since 0.10.0
	 */
	default Arguments4Validator<A1, A2, A3, A4, Supplier<X>> lazy() {
		throw new UnsupportedOperationException("lazy is not implemented!");
	}

	default Validated<X> validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4) {
		return this.validate(a1, a2, a3, a4, Locale.getDefault(), ConstraintGroup.DEFAULT);
	}

	default Validated<X> validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4,
			ConstraintContext constraintContext) {
		return this.validate(a1, a2, a3, a4, Locale.getDefault(), constraintContext);
	}

	default Validated<X> validate(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4, Locale locale) {
		return this.validate(a1, a2, a3, a4, locale, ConstraintGroup.DEFAULT);
	}

	default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4)
			throws ConstraintViolationsException {
		return this.validate(a1, a2, a3, a4).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4,
			ConstraintContext constraintContext) throws ConstraintViolationsException {
		return this.validate(a1, a2, a3, a4, constraintContext).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4, Locale locale)
			throws ConstraintViolationsException {
		return this.validate(a1, a2, a3, a4, locale).orElseThrow(ConstraintViolationsException::new);
	}

	default X validated(@Nullable A1 a1, @Nullable A2 a2, @Nullable A3 a3, @Nullable A4 a4, Locale locale,
			ConstraintContext constraintContext) throws ConstraintViolationsException {
		return this.validate(a1, a2, a3, a4, locale, constraintContext).orElseThrow(ConstraintViolationsException::new);
	}

}
